package il.ac.tau.cvws.scrabblesidekick.server.engine.scrabble.detector;

import il.ac.tau.cvws.scrabblesidekick.server.db.engine.MySQLDBEngine;
import il.ac.tau.cvws.scrabblesidekick.server.engine.dictionary.exception.DictionaryException;
import il.ac.tau.cvws.scrabblesidekick.server.util.exception.ExceptionConstant;
import il.ac.tau.cvws.scrabblesidekick.server.util.exception.ScrabbleSidekickException;
import il.ac.tau.cvws.scrabblesidekick.shared.constant.BoardConstants;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.WordPlacement;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Direction;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Position;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Rack;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.Tile;
import il.ac.tau.cvws.scrabblesidekick.shared.datatype.game.TileLayout;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

public abstract class SolutionDetector
{
	protected MySQLDBEngine dbEngine;

	public SolutionDetector()
	{
		this.dbEngine = new MySQLDBEngine();
	}
	
	/*
	 * LOGIC: The function validates if a given suggestion for the current layout on board is correct according to the specific dictionary. Tests are made depending on the direction of
	 *        the suggestion.
	 * INPUT: int[] possible - the given suggestion, layout- the current layout, startpos - the starting position in the board of suggestion, length- the size of the given suggestion,
	 *        dir - the direction of the given suggestion: Horizontal or vertical, dictionaryId- the identifier of the given dictionary in DB, isLeft2Right- reading order, if the reading
	 *        order of the language is not from left to right, some reversing operation need to be done in order to search in dictionary. 
	 * RETURN: True- if the suggestion is correct and valid in the given position, otherwise, False.
	 */
	protected boolean validateSolution(int[] possible, TileLayout layout, Position startPos,
			int length, Direction dir, int dictionaryID, boolean isLeftToRight)
		throws ScrabbleSidekickException
	{
		boolean foundPossibleWord = false;
		boolean wordIsValid       = false;

		if (dir == Direction.HORIZONTAL)
		{
			foundPossibleWord  = checkHorizontal(possible, layout, startPos, length, dir, dictionaryID, isLeftToRight);
			wordIsValid        = foundPossibleWord & checkLongRow(possible, layout, startPos, length, dir, dictionaryID, isLeftToRight);
		}
		else
		{
			foundPossibleWord  = checkVertical(possible, layout, startPos, length, dir, dictionaryID, isLeftToRight);
			wordIsValid        = foundPossibleWord & checkLongCol(possible, layout, startPos, length, dir, dictionaryID, isLeftToRight);
		}
		
		return (foundPossibleWord && wordIsValid);
	}
	/*
	 * LOGIC: The function checks if the given horizontal suggestion keep the validity of the layout by checking the adjacent vertical tiles of each tile on suggestion.
	 * 		  for example, given the horizontal option: ROOM and according to the start pos should be placed like that in layout
	 * 			      
	 * 		S H O R T				S H O R T
	 *     		    O 				    R O O M 
	 *       	  T R E E				  T R E E
	 * 				N						N
	 * 		  		A						A
	 * 		 	    D						D
	 * 		 		O		 	        	O
	 * 		 the function will iterate over each letter and check if the new created word exist in dictionary. if all the new created words exist in the dictionary the function will
	 * 	     return true, as the given suggestion is correct and valid in the given position. In the example above the function would return TRUE because the words 
	 * 		 OR, ME and ROT exist in the English dictionary
	 *       Notice: the new words have to be in the predefined dictionary, otherwise it will be considered as the word doesn't exist in the given dictionary language.
	 * INPUT: int[] possible - the given suggestion, layout- the current layout, startpos - the starting position in the board of suggestion, length- the size of the given suggestion,
	 *        dir - the direction of the given suggestion: Horizontal or vertical, dictionaryId- the identifier of the given dictionary in DB, isLeft2Right- reading order, if the reading
	 *         order of the language is not from left to right, some reversing operation need to be done in order to search in dictionary. 
	 * RETURN: TRUE, if the given horizontal suggestion is correct and valid in startpos position, otherwise, False.
	 */
	public boolean checkHorizontal(int[] possible, TileLayout layout, Position startPos,
			int length, Direction dir, int dictionaryID,  boolean isLeftToRight)
		throws ScrabbleSidekickException
	{
		int row   =  startPos.getRow();
		int len   =  startPos.getColumn() + length;

		List<int[]>   found 	= null;
		List<Integer> foundWord = new ArrayList<Integer>();

		for (int i = startPos.getColumn(); i < len ; i++ )
		{
			if (layout.isPosInLayout(new Position(row , i)))
			{
				continue;
			}

			int back = row, forw = row;

			while (layout.isPosInLayout(new Position(back - 1, i)))
			{
				back--;
				
				if (back == 0)
				{
					break;
				}
			}
			
			while (layout.isPosInLayout(new Position(forw + 1, i)))
			{
				forw++;

				if (forw == 14)
				{
					break;
				}
			}

			if (forw - back != 0)
			{
				for (int j = back; j <= forw ; j++)
				{
					if (j == startPos.getRow())
					{
						foundWord.add(possible[i - startPos.getColumn()]);
					}
					else
					{
						foundWord.add(layout.getTileByPos(new Position(j, i)).getIndex());
					}
				}

				try
				{
					found = dbEngine.findWordTemplate(dictionaryID, foundWord);
				}
				catch (SQLException e)
				{
					throw new DictionaryException(ExceptionConstant.GENERAL_FAILURE);
				}

				if (found.size() == 0)
				{
					return false;
				}
			}
		}
		
		return true;
	}
	
	/*
	 * LOGIC: The function checks if the given vertical suggestion keep the validity of the layout by checking the adjacent horizontal tiles of the new suggestion.
	 * 		  for example, given the horizontal option: GONE and the given start pos, GONE should be placed in the following manner:
	 * 	Before:						After:	      
	 *        		                     G		  		
	 *        		                     O		  			
	 * F A I N T                   F A I N T 
	 * O						   O     E
	 * G A I N					   G A I N 
	 * 		 O                           O
	 *  the function checks if the new created word has adjacent tiles in layout and will concatenate to the new word the old tiles in the start and in the end of the word. Than,
	 *  the function validate that the concatenated word exist in the given dictionary. In the example above the function will validate the word GONENO which is definitely not in 
	 *  the English dictionary and therefore, it will return FALSE.
	 * INPUT: int[] possible - the given suggestion, layout- the current layout, startpos - the starting position in the board of suggestion, length- the size of the given suggestion,
	 *        dir - the direction of the given suggestion: Horizontal or vertical, dictionaryId- the identifier of the given dictionary in DB, isLeft2Right- reading order, if the reading
	 *         order of the language is not from left to right, some reversing operation need to be done in order to search in dictionary. 
	 * RETURN: TRUE, if the concatenated word exist in dictionary, otherwise, FALSE.
	 */
	public boolean checkLongRow(int[] possible, TileLayout layout, Position startPos,
			int length, Direction dir, int dictionaryID, boolean isLeftToRight)
		throws ScrabbleSidekickException
	{
		int row  = startPos.getRow();
		int back = startPos.getColumn() - 1;
		int forw = startPos.getColumn() + length;

		List<int[]> found = null;

		List<Integer> foundWord = new ArrayList<Integer>();
		
		while (back != -1 && layout.isPosInLayout(new Position(row, back)))
		{
			back--;
		}

		while (forw != BoardConstants.BOARD_TILES_NUM && layout.isPosInLayout(new Position(row, forw)))
		{
			forw++;
		}

		back++;
		forw--;

		if ( forw - back >   length - 1)
		{
			for(int j = back; j <= forw ; j++)
			{
				if(j >= startPos.getColumn() && j <= startPos.getColumn() + length - 1)
				{
					foundWord.add(possible[j - startPos.getColumn()]);
				}
				else
				{
					foundWord.add(layout.getTileByPos(new Position(row, j)).getIndex());
				}
			}

			try
			{
				found = dbEngine.findWordTemplate(dictionaryID, foundWord);
			}
			catch (SQLException e)
			{
				throw new DictionaryException(ExceptionConstant.GENERAL_FAILURE, e);
			}

			if (found.size() == 0)
			{
				return false;
			}
		}
		
		return true;
	}
	
	/*
	 * LOGIC: The function checks if the given vertical suggestion keep the validity of the layout by checking the adjacent horizontal tiles of the new suggestion.
	 * 		  for example, given the horizontal option: GONE and the given start pos, GONE should be placed in the following manner:
	 * 	Before:						After:	      
	 *        		                     G		  		
	 *        		                     O		  			
	 * F A I N T                   F A I N T 
	 * O						   O     E
	 * G A I N					   G A I N 
	 * 		 O                           O
	 *  the function checks if the new created word has adjacent tiles in layout and will concatenate to the new word the old tiles in the start and in the end of the word. Than,
	 *  the function validate that the concatenated word exist in the given dictionary. In the example above the function will validate the word GONENO which is definitely not in 
	 *  the English dictionary and therefore, it will return FALSE.
	 * INPUT: int[] possible - the given suggestion, layout- the current layout, startpos - the starting position in the board of suggestion, length- the size of the given suggestion,
	 *        dir - the direction of the given suggestion: Horizontal or vertical, dictionaryId- the identifier of the given dictionary in DB, isLeft2Right- reading order, if the reading
	 *         order of the language is not from left to right, some reversing operation need to be done in order to search in dictionary. 
	 * RETURN: TRUE, if the concatenated word exist in dictionary, otherwise, FALSE.
	 */
	public boolean checkLongCol(int[] possible, TileLayout layout, Position startPos,
			int length, Direction dir, int dictionaryID, boolean isLeftToRight)
		throws ScrabbleSidekickException
	{
		int col  = startPos.getColumn();
		int back = startPos.getRow() - 1;
		int forw = startPos.getRow() + length;

		List<int[]> found = null;
		List<Integer> foundWord = new ArrayList<Integer>();

		while (back != -1 && layout.isPosInLayout(new Position(back, col)))
		{
			back--;
		}

		while (forw != BoardConstants.BOARD_TILES_NUM && layout.isPosInLayout(new Position(forw, col)))
		{
			forw++;
		}

		back++;
		forw--;

		if (forw - back > length - 1)
		{
			for (int j = back; j <= forw ; j++)
			{
				if (j >=startPos.getRow() && j <= startPos.getRow() + length - 1)
				{
					foundWord.add(possible[j - startPos.getRow()]);
				}
				else
				{
					foundWord.add(layout.getTileByPos(new Position(j, col)).getIndex());
				}
			}

			try
			{
				found = dbEngine.findWordTemplate(dictionaryID, foundWord);
			}
			catch (SQLException e)
			{
				throw new DictionaryException(ExceptionConstant.GENERAL_FAILURE);
			}

			if(found.size() == 0)
			{
				return false;
			}
		}
		return true;
	}
	/*
	 * LOGIC: The function checks if the given vertical suggestion keep the validity of the layout by checking the adjacent horizontal tiles of the new suggestion.
	 * 		  for example, given the horizontal option: ODOR and the given start pos, ODOR should be placed in the following manner:
	 * 
	 *  	B O X E R	  		B O X E R
	 * 	  B E                 B E*D*    
	 * 		T 					T*O*
	 * 		      				 *R*
	 * the function will iterate over each letter and check if the new created word exist in dictionary. if all the new created words exist in the dictionary the function will
	 * return true, as the given suggestion is correct and valid in the given position. In the example above the function would return TRUE because the words 
	 * BED and TO exist in the English dictionary
	 * Notice: the new words have to be in the predefined dictionary, otherwise it will be considered as the word doesn't exist in the given dictionary language.
	 * INPUT: int[] possible - the given suggestion, layout- the current layout, startpos - the starting position in the board of suggestion, length- the size of the given suggestion,
	 * dir - the direction of the given suggestion: Horizontal or vertical, dictionaryId- the identifier of the given dictionary in DB, isLeft2Right- reading order, if the reading
	 * order of the language is not from left to right, some reversing operation need to be done in order to search in dictionary. 
	 * RETURN: TRUE, if the given vertical suggestion is correct and valid in startpos position, otherwise, False.
	 */
	public boolean checkVertical(int[] possible, TileLayout layout, Position startPos,
			int length, Direction dir, int dictionaryID, boolean isLeftToRight)
		throws ScrabbleSidekickException
	{
		int col = startPos.getColumn();
		int len = startPos.getRow() + length;

		List<int[]> found = null;

		List<Integer> foundWord = new ArrayList<Integer>();

		for (int i = startPos.getRow(); i < len ; i++ )
		{
			if (layout.isPosInLayout(new Position(i, col)))
			{
				continue;
			}

			int back = col;
			int forw = col;
			
			while (layout.isPosInLayout(new Position(i, back - 1)))
			{
				back--;

				if (back == 0)
				{
					break;
				}
			}

			while (layout.isPosInLayout(new Position(i, forw + 1)))
			{
				forw++;

				if (forw == (BoardConstants.BOARD_TILES_NUM - 1))
				{
					break;
				}
			}

			if( forw - back != 0)
			{		
				for(int j = back; j <= forw ; j++)
				{
					if(j == startPos.getColumn())
					{
						foundWord.add(possible[i - startPos.getRow()]);
					}
					else
					{
						foundWord.add(layout.getTileByPos(new Position(i, j)).getIndex());
					}				
				}

				if (isLeftToRight == false)
				{
					Collections.reverse(foundWord);
				}

				try
				{
					found = dbEngine.findWordTemplate(dictionaryID, foundWord);
				}
				catch (SQLException e)
				{
					throw new DictionaryException(ExceptionConstant.GENERAL_FAILURE);
				}

				if (found.size() == 0)
				{
					return false;
				}
			}
		}
		
		return true;
	}	

	/* LOGIC:the function creates searching template derived from layout, startpos and direction.
	 * for each cell in the template the function adds the empty character (if no tile on cell) or a letter(if there is an existing tile on board)
	 * INPUT:layout- the current layout, startpos - the starting position in the board of template, length- the size of the given template,
	 * direction - the direction of the given suggestion: Horizontal or vertical,
	 * RETURN: the function returns the created template.
	 */
	public List<Integer> createWordTemplate(TileLayout layout, Position startPos, int length, Direction direction)
	{
		List<Integer> lst = new ArrayList<Integer>();

		if(direction == Direction.VERTICAL)
		{
			int maxRow = Math.min(startPos.getRow() + length, 15);
			int column = startPos.getColumn();

			for (int i = startPos.getRow() ; i < maxRow; i ++ )
			{
				Tile tile = layout.getTileByPos(new Position(i, column));

				if (tile != null)
				{
					lst.add(tile.getIndex());
				}
				else
				{
					lst.add(BoardConstants.EMPTY_TILE);
				}
			}	
		}
		else
		{
			int maxColumn = Math.min(startPos.getColumn() + length, 15);
			int row 	  = startPos.getRow();

			for (int i = startPos.getColumn() ; i < maxColumn; i ++ )
			{
				Tile tile = layout.getTileByPos(new Position(row,i));

				if(tile != null)
				{
					lst.add(tile.getIndex());
				}
				else
				{
					lst.add(BoardConstants.EMPTY_TILE);
				}
			}	
		}

		return lst;
	}
	/* LOGIC:the function searches for words that fit the given template. e.g for the template  _ _ M_ _ R the function will search in the given dictionary(identified by dictionaryId)
	 * words that consist only 6 letters, the third letter is M and the last one is R. (like HUMOUR)
	 * After finding such words the function checks for each option if the other letters, which don't appear on template are on the user's rack.
	 * INPUT: rack - the user's rack, List<Integer> wordTemplate- the given template, int dictionaryId - the given dictionary
	 * RETURN: the function returns a list of all the words which fit the template and the other letters are on the user's rack.
	 */
	protected List<int[]> findSolutions(Rack rack, List<Integer> wordTemplate, int dictionaryId) throws ScrabbleSidekickException
	{
		List<int[]> options = null;

		try
		{
			options = dbEngine.findWordTemplate(dictionaryId, wordTemplate);
		}
		catch (SQLException e)
		{
			throw new DictionaryException(ExceptionConstant.GENERAL_FAILURE);
		}

		List<int[]> solutions = new ArrayList<int[]>();

		for (int[] option: options)
		{
			if (isOnRack(rack, option, wordTemplate) == true) 
			{
				solutions.add(option);
			}
		}

		return solutions;
	}
	/* LOGIC:the function gets a word which fits a given template and verify if the other letters(those who are not on the template already) are on the user's rack.
	 * the function also count the number of Jokers the user have. By this the function knows how many misses are permitted.
	 * the function checks that each letter on rack is used only once. 
	 * INPUT: rack - the user's rack, List<Integer> wordTemplate- the given template, int[] option- the given word which fits the wordTemplate
	 * RETURN: the function returns TRUE if the following conditions are TRUE
	 * 1. letters of option that are not on board have to be on Rack
	 * 2. number of missing letters of option is equal to number of jokers = blank tiles. OR number of missing letters = 0
	 * 3. At least one tile is added from rack (otherwise the word is already on board and the user won't do a move).
	 */
	private boolean isOnRack(Rack rack, int[] option, List<Integer> wordTemp) 
	{
		int[] rackLetters = rack.getTileIndices();
		int misses = 0; 
		int jokerCount = 0;
		int tilesFromRack = option.length;
		boolean[] isUsed = new boolean[rack.rackLength()];
		
		for (int j = 0 ; j < rack.rackLength(); j ++ )
		{
			if (rackLetters[j] == BoardConstants.EMPTY_TILE)
			{
				jokerCount++;
			}
		}

		for (int i = 0; i < option.length; i++)
		{
			if (wordTemp.get(i) == BoardConstants.EMPTY_TILE)
			{
				boolean exist = false;
				for (int j = 0 ; j < rack.rackLength(); j ++ )
				{	
					if ((option[i] == rackLetters[j]) && (isUsed[j] == false))
					{
						exist = true;
						isUsed[j] = true;
						break;
					}	
				}

				if(exist == false)
				{
					misses++;
				}
			}
			else
			{
				tilesFromRack--;
			}
		}

		if (tilesFromRack == 0)
		{
			return false;
		}
		
		return ((misses == 0)|| (misses == jokerCount));
	}	

	public abstract List<WordPlacement> detect(Set<Position> layoutBoundries, TileLayout layout, int dictionaryId,Rack rack) throws ScrabbleSidekickException;
}

